fix(analytics): make organization timezone drive date-dimension bucketing (#1982)#2018
Merged
Conversation
…ting (#1982) Date-bucketed analytics silently ignored the reference timezone end-to-end. Three independent seams were broken; all three are needed for an org's localization timezone to actually change a date-bucketed chart: 1. service-analytics — NativeSQLStrategy (priority 10) won every cube/dataset query on a SQL driver, but it groups by the raw column (no date_trunc) and ignores `timezone`, so date dimensions never bucketed (one row per raw timestamp) and a non-UTC zone was dropped. It now declines queries carrying a timeDimensions[].granularity, handing them to ObjectQLStrategy → engine.aggregate (native bucketing UTC-safe, uniform in-memory when non-UTC). 2. objectql — the in-memory `count` aggregation treated the `*` count-all sentinel (Cube `count` measure / fieldless dataset `count`, both compiled to sql:'*') as a column name → counted 0 for every bucket. The driver COUNT(*) masked it; the in-memory path (non-UTC date buckets, driver-rest/-memory) returned zeros. `*` is now counted as all rows. 3. rest — resolveExecCtx never resolved the localization timezone/locale, so /analytics/dataset/query always ran with timezone:'UTC'. It now resolves them through the settings service (4-tier cascade incl. the OS_LOCALIZATION_TIMEZONE env override), mirroring the dispatcher path. Verified end-to-end against example-crm: an org tz of America/Los_Angeles now buckets a 03:5x UTC lead into 2026-06-17 (cnt 5) vs UTC's 2026-06-18 (cnt 5). Regression tests added for seams 1 and 2; full suites green (objectql 660, service-analytics 127, rest 121). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 3 package(s): 19 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
added a commit
that referenced
this pull request
Jun 18, 2026
Static gates (build, unit tests, spec-liveness, CodeQL) verify each layer in isolation, usually against mocks, and cannot catch a break that only appears when the real engine + strategies + services + HTTP context run together. The #2018 tz-bucketing regression is the canonical case: green on every static gate (900+ unit tests included) because each of its three broken seams was individually correct and individually mocked. This adds `@objectstack/dogfood` (private), a harness that boots real example apps in-process against in-memory SQLite — wired with the same service plugins `objectstack dev` loads — and exercises them through the real HTTP surface via Hono request-injection (no ports, no sockets, CI-stable). Tests act as a browser client: sign in, hit /api/v1/..., assert on real responses. - src/harness.ts — bootDogfoodStack(config) → { kernel, api, raw, signIn, apiAs, stop } - test/analytics-timezone.dogfood.test.ts — golden regression for #1982/#2018: org timezone (set via the real settings route) must shift a boundary-crossing instant's date bucket (UTC 2024-03-01 → America/Los_Angeles 2024-02-29) with the correct count. Verified to FAIL when any #2018 seam is reverted. - CI: new "Dogfood Regression Gate" job (also runs under turbo run test). Boot-to-assert ~2s. Private package — no changeset. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 18, 2026
…2024) * docs(adr): ADR-0054 prove-it-runs gate for the authorable surface Extends ADR-0049 (enforce-or-remove) with a third leg. The liveness ledger (#1919) classifies every authorable property live/experimental/dead, but "live" means only a static file:line consumer pointer — proof that something reads the property, not that authoring it produces correct runtime behavior. #2018 (tz bucketing: live at every layer, broken in integration) and the field-type fidelity gaps (#2022: rating/slider/toggle read back wrong-typed) fell through that gap — call it "unproven liveness". For a platform whose authors are AI emitting metadata across a combinatorial space the examples never cover, unproven liveness ships silently into third-party apps. ADR-0054 upgrades a `live` classification to optionally carry a `proof` — a @objectstack/dogfood test that authors the property against the real in-process stack and asserts the runtime outcome. Required as a ratchet (not a retrofit) for a high-risk authorable class on change, and for any property implicated in a shipped regression (the fix carries its proof). Generative testing is explicitly deferred (Phase 3, evidence-gated). Proposed — for architect review. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(adr): accept ADR-0054 (prove-it-runs gate) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
pushed a commit
that referenced
this pull request
Jun 19, 2026
…0054) Unlocks the analytics binding that was previously blocked because its authorable surface wasn't governed. - Govern the `dataset` metadata type: new liveness/dataset.json classifies all 19 authorable props with file:line evidence from the analytics service consumer audit (26 live, 1 dead — measures.certified has no runtime consumer). `dataset` added to GOVERNED in check-liveness.mts. - Bind the analytics class: dataset.dimensions.dateGranularity carries the tz-bucketing proof (#1982/#2018) — the property whose org-timezone day-bucket behavior the proof asserts. proof-registry.mts flips analytics to bound. - Four high-risk classes now CI-enforced: field types, RLS, flow nodes, analytics. - README governed-types + high-risk tables updated; changeset description updated. Verified: liveness gate green (dataset 27 classified); 17 registry tests; the analytics proof runs green; full dogfood suite 59/59. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx
os-zhuang
added a commit
that referenced
this pull request
Jun 19, 2026
…h-risk classes (ADR-0054 #1 + Phase 2) (#2045) * feat(spec): add prove-it-runs proof field + ratchet to liveness gate (ADR-0054 #1) ADR-0054 follow-up (1): the liveness ledger's `live` meant only a static consumer pointer — necessary but not sufficient, since a property can be live at every layer yet broken end-to-end. This adds the third leg: high-risk authorable properties must carry a `proof` (a dogfood test reference) that asserts the runtime outcome. - proof-registry.mts: the authoritative high-risk-class list (field types, analytics, RLS, flow nodes, form widgets) + which classes the ratchet enforces this phase. Field types and RLS are bound (matrix exists AND surface governed); analytics/flow/form are listed-but-blocked with honest reasons (their surface isn't governed yet / no proof yet — Phase 2). - check-liveness.mts: a bound `live` entry must carry a valid `proof` of its own class. Validation is STATIC (file exists + declares the `@proof: <id>` tag) so the gate stays seconds-cheap; running the proof remains the dogfood gate's job. Reverse check flags unregistered `@proof:` tags. - Ledger: field.type → field-zoo proof; permission.rowLevelSecurity.using → #1994 RLS proof. Dogfood proofs self-declare their `@proof:` tag. - Gate now also triggers on packages/dogfood/** so deleting/renaming a proof re-runs the check and the dangling reference is caught. - 15 unit + wiring tests; README documents the contract and ratchet. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * chore: add changeset for the liveness prove-it-runs gate change Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * feat(verify,spec): bind flow-node high-risk class with a runtime proof (ADR-0054 Phase 2) Phase 2 extends the prove-it-runs ratchet to the flow-node class. - verify: bootStack gains an opt-in `automation` flag that registers @objectstack/service-automation, so authored flows are pulled from the registry and POST /automation/:name/trigger runs their nodes. Without it, flow execution was unreachable through the harness (the dispatcher's automation routes resolved no service). Mirrors the existing `multiTenant`/`security` opt-ins; default off. - dogfood: a self-contained flow fixture (one object + one autolaunched flow whose update_record node stamps a record) + the flow-node proof. It authors the flow, triggers it over HTTP, and asserts both directions — the targeted record is stamped (node executed) AND a bystander is untouched (the input variable wired into the node filter, not a blanket update). Runs green end-to-end. - spec: flow-node class is now `bound` in proof-registry.mts; flow.nodes.type carries the proof; the liveness gate enforces it. Three classes now bound: field types, RLS, flow nodes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx * feat(spec): govern dataset + bind the analytics high-risk class (ADR-0054) Unlocks the analytics binding that was previously blocked because its authorable surface wasn't governed. - Govern the `dataset` metadata type: new liveness/dataset.json classifies all 19 authorable props with file:line evidence from the analytics service consumer audit (26 live, 1 dead — measures.certified has no runtime consumer). `dataset` added to GOVERNED in check-liveness.mts. - Bind the analytics class: dataset.dimensions.dateGranularity carries the tz-bucketing proof (#1982/#2018) — the property whose org-timezone day-bucket behavior the proof asserts. proof-registry.mts flips analytics to bound. - Four high-risk classes now CI-enforced: field types, RLS, flow nodes, analytics. - README governed-types + high-risk tables updated; changeset description updated. Verified: liveness gate green (dataset 27 classified); 17 registry tests; the analytics proof runs green; full dogfood suite 59/59. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVdnfUAx85amkerym26vdx --------- Co-authored-by: Claude <noreply@anthropic.com>
os-zhuang
added a commit
that referenced
this pull request
Jun 27, 2026
feat(console): "Edit with AI" — open the build agent pre-scoped to an app's package (#2018) objectui@2021b5f4a89c1940b2b3aa751ce4e33fe971974a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A follow-up to ADR-0053 Phase 2 (#1982) browser verification: setting a non-UTC organization timezone (localization manifest, #2006) did not change a date-bucketed analytics chart. The unit tests for the tz bucketing engine passed, but the result never reached the API — the classic "passes build / silent at runtime" gap.
Tracing it revealed three independent broken seams, all required for an org timezone to actually drive a date bucket:
@objectstack/service-analytics— wrong strategy won.NativeSQLStrategy(priority 10) handled every cube/dataset query on a SQL driver, but it groups by the raw column (GROUP BY <col>, nodate_trunc) and referencestimezonenowhere. So date dimensions never bucketed (one row per raw timestamp) and a non-UTC zone was silently dropped. It now declines queries carrying atimeDimensions[].granularity, handing them to the lower-priorityObjectQLStrategy→engine.aggregate(native bucketing when UTC-safe, uniform in-memory bucketing when non-UTC).@objectstack/objectql— count-all returned 0 in-memory. The Cubecountmeasure and a fieldless datasetcountboth compile tosql: '*'. The in-memorycountbranch treated'*'as a column name and counted non-null of a non-existent property → 0 for every bucket. The driver'sCOUNT(*)masked it, but the in-memory path (non-UTC date buckets,driver-rest/driver-memory) returned zeros.'*'is now counted as all rows.@objectstack/rest— context had no timezone.resolveExecCtxnever resolved the localization timezone/locale, so/analytics/dataset/queryalways ran withtimezone: 'UTC'. It now resolves them through thesettingsservice (honouring the 4-tier cascade incl. theOS_LOCALIZATION_TIMEZONEenv override), mirroring the dispatcher path'sresolveLocalization.Verification
End-to-end against
example-crmwith a lead created at…T03:5x:…Z(UTC day06-18, LA day06-17):06-18cnt 506-18cnt 5America/Los_Angeles(settings)06-18cnt 5 ❌06-17cnt 5 ✅selection.timezone=America/LA06-17×… cnt 0 ❌ (uncollapsed)06-17cnt 5 ✅Tests
native-sql-granularity-decline.test.ts— pins the production capability shape (nativeSql: trueANDobjectqlAggregate: true); a granularity query must route toexecuteAggregate, not raw SQL. (The pre-existing granularity test forcednativeSql:false, which masked the bug.)in-memory-aggregationcases for the'*'count-all sentinel (ungrouped + per tz bucket).🤖 Generated with Claude Code